-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Interface as simple as possible #13
base: main
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## main #13 +/- ##
=========================================
+ Coverage 0 29.91% +29.91%
=========================================
Files 0 13 +13
Lines 0 117 +117
=========================================
+ Hits 0 35 +35
- Misses 0 82 +82
|
|
||
$(TYPEDFIELDS) | ||
""" | ||
mutable struct SimpleDiGraph{T<:Integer} <: AbstractGraph{T,SimpleEdge{T}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it mutable? I guess to allow in-place modification of ne
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
presumably but it's kinda sad, I'm curious if there's a better way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe ne::Base.RefValue{Int}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that was my thought at first, and I think I suggested it somewhere but got corrected, if only I could find the issue...
then again, maybe it's time to start fresh!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess in newer versions of Julia you can mark fadjlist
and badjlist
as const
so it may not matter conceptually either way. I think I found using Base.RefValue
was a bit slower in practice compared to mutating a field of a mutable
.
return (SimpleEdge{T}(u, v) for v in g.adjlist[u]) | ||
end | ||
|
||
GraphsBase.in_edges(g::SimpleGraph, v) = out_edges(g, v) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about reverse.(out_edges(g, v))
?
Something I found slightly annoying in Graphs.jl
was that SimpleEdge
was sometimes treated as directed and sometimes not, which leads to weird corner cases when designing code that is generic between undirected and directed graphs. Ideally, I think an undirected graph should act as much like a directed graph that has edges in both directions between vertices that have edges. So from that perspective it may be helpful to have in_edges
and out_edges
output edges with reverse directions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
GraphsBase.src(e::SimpleWeightedEdge) = e.src | ||
GraphsBase.dst(e::SimpleWeightedEdge) = e.dst | ||
GraphsBase.weight(e::SimpleWeightedEdge) = e.weight |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have examples where the weight would be determined in some potentially non-trivial way from vertex metadata, so it doesn't seem so natural to store it on an edge.
What about allowing weight(g::AbstractGraph, e::AbstractEdge)
? It could then have a fallback weight(e::AbstractEdge)
in the cases when it can be determined just from an edge.
With the current design the only way to customize the weight
function is by defining a new edge type, which doesn't seem so practical in general (i.e. I would want to customize by graph type or graph instance, say by storing a weight function as a field inside the graph type which I access when overloading weight
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry for the laconic answers, I'm just opening issues as I go to keep track of your ideas in a more discoverable location
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, thanks. I'm sure these same issues/questions have been brought up before, sorry for rehashing things. It's definitely more helpful to look at a concrete code proposal rather than longer abstract discussions, so thanks for making this PR!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@etiennedeg deserves more credit, my PR is a simplified, structured version of his
GraphsBase.src(e::SimpleWeightedEdge) = e.src | ||
GraphsBase.dst(e::SimpleWeightedEdge) = e.dst | ||
GraphsBase.weight(e::SimpleWeightedEdge) = e.weight | ||
Base.reverse(e::SimpleWeightedEdge) = SimpleEdge(e.dst, e.src, e.weight) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guess this should be SimpleWeightedEdge(...)
?
function GraphsBase.in_edges(g::SimpleWeightedDiGraph, v) | ||
A = g.weights | ||
a = A[v, :] | ||
return (SimpleWeightedEdge{T}(u, v, a[u]) for u in a.nzind) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this points to a challenge of coupling metadata with edges.
What if your graph requires some non-trivial calculation to access metadata (I can definitely think of situations where that is the case)? Every time you ask for an edge, you would need to perform that computation, even though most graph functions don't need the metadata and probably just want the incident vertices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The g = SimpleGraph{Int}()
add_vertex!(g, 1_000_000) ...but the vector of vectors data structure is not well-suited for that sort of usage. In other words, in my opinion, the generic interface should guarantee that |
That's not for the interface to impose, that's a matter of implementation. The interface just provides the method
|
Julia interfaces provide more than just method signatures, they also provide concepts. Here is the concept that I have in mind for adding and removing vertices:
In my opinion, in the new interface this concept should apply to all subtypes of For clarity, here's an example that shows what I expect to happen: # vertices(g) == [1, 2, 3, 4, 5]
rem_vertex!(g, 3) # now `vertices(g) == [1, 2, 4, 5]
add_vertex!(g, 42) # now `vertices(g) == [1, 2, 4, 5, 42] |
@CameronBieganek I don't see why adding any vertex should be allowed for any graph type, since that won't even be possible in many cases (say adding a string vertex to a graph that only supports integer vertices). I think it's reasonable to throw an error if you try to add a vertex that isn't compatible with the data structure (or isn't fast to add), as @gdalle suggested above. |
That was just an imprecision in my language above. I mean any vertex of a compatible type. The precise definition is contained in my docstring for
So, you can add an julia> push!(Set(1), 'a')
ERROR: ArgumentError: 'a' is not a valid key for type Int64
Stacktrace:
[1] setindex!(h::Dict{Int64, Nothing}, v0::Nothing, key0::Char)
@ Base .\dict.jl:363
[2] push!(s::Set{Int64}, x::Char)
@ Base .\set.jl:103
[3] top-level scope
@ REPL[1]:1 |
I still don't see why we wouldn't allow graph types (like |
The behavior of ...I know that |
Agreed that the behavior of In fact, I searched for
so I don't see why supporting them would be an issue. It seems like if a user doesn't like the behavior of Footnotes |
I appreciate the sentiment that functions should have contracts, and it could make sense that
Then, there could be some internal function for removing the vertices of a |
Here is a proposal for generic linear indexing of graphs. For graphs with linear vertices (like add_vertex!(g, v) # Only works if `v == nv(g) + 1`
rem_vertex!(g, v) # Only works if `v == nv(g)` and for more general graphs, there would be fewer constraints on To add or remove arbitrary vertices of a insert_vertex!(g, v) # Analogous to `insert!` for inserting an element into a `Vector` in Base Julia
add_vertex!(g, v * th) # Syntax from https://github.com/jishnub/OrdinalIndexing.jl for linear indexing
delete_vertex!(g, v) # Analogous to `deleteat!` for deleting an element of a `Vector` in Base Julia
rem_vertex!(g, v * th) # Syntax from https://github.com/jishnub/OrdinalIndexing.jl for linear indexing These could generalize to a graph with general vertices that has an underlying data structure that has linear vertices (say a graph with arbitrary vertices wrapping a |
nv | ||
ne |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really wish these horrible names would be removed or at least deprecated...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nvertices
and nedges
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would at least fit with ndigits
, nfields
, ndims
.
(Personally I've come to loath all these abbreviations -- they are super nice if you bang away on experiments in the REPL. But write code with them and come back after a year and try to read it, and it looks like gobbledygook. Esp. if you have to context switch between multiple languages and libraries... So I am now tending into the more "radical" direction of preferring names like num_vertices
or even number_of_vertices
. But I realize I am probably in the minority on this, which is fine, but I figure I should at least mention it once before staying silent forever after ;-). Anyhow, thank you for working on this ).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, nvertices
and nedges
seems decent.
Haven't read everything, but I think the following was still not raised as a point: Given that MetaGraphs will be integrated in GraphBase isn't it a bit a redundant to also have WeightedGraph implemented differently ? I mean a weighted graph is nothing else but a MetaGraph with a primitive data type as edge data. |
Based on #9, here is a smaller PR that will hopefully be easier to review and merge as a starting point.
Here are the highlights:
AbstractEdge
(4 functions):src
,dst
,weight
,reverse
AbstractGraph
(4 functions):is_directed
,vertices
,out_edges
,in_edges
AbstractGraph
, which the users are free to implement any subset of (since everything has a slow fallback)Simple(Di)Graph
andSimpleWeighted(Di)Graph
as examplesIf you want to review, the easiest starting point is the documentation preview at https://juliagraphs.org/GraphsBase.jl/previews/PR13
Related issues: